package com.example.core.datasource;

import com.example.core.context.CommonContext;
import com.example.core.context.CustomContext;
import com.zaxxer.hikari.HikariDataSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;

import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Map;

public class DynamicDataSource extends AbstractRoutingDataSource implements ApplicationContextAware {

    private Logger logger = LoggerFactory.getLogger(DynamicDataSource.class);

    @Value("${spring.datasource.hikari.max-lifetime:90000}")
    private long maxLifeTime;
    @Value("${spring.datasource.hikari.minimum-idle:0}")
    private int minimumIdle;
    @Value("${spring.datasource.hikari.maximum-pool-size:20}")
    private int maximumPoolSize;
    @Value("${spring.datasource.hikari.connection-timeout:60000}")
    private long connectionTimeout;

    private ApplicationContext applicationContext;

    @Override
    protected Object determineCurrentLookupKey() {
        CommonContext content = CommonContext.getContext();
        CustomContext customContent = content.getCustomContext();
        if (ObjectUtils.isEmpty(customContent)) {
            throw new RuntimeException("get app data source error : is not init common context");
        } else {
            String dataSourceApp = customContent.getDataSourceApp();
            if (StringUtils.isEmpty(dataSourceApp)) {
                dataSourceApp = DynamicDataSourceUtil.getDefaultSource();
            }
            logger.debug("connection dataSource: {}", dataSourceApp);
            Map<Object, Object> map;
            try {
                map = this.getTargetDataSources();
            } catch (NoSuchFieldException var11) {
                this.logger.error("get target data-sources exception:", var11);
                throw new RuntimeException(String.format("get target data-sources exception [%s]", var11.getMessage()));
            } catch (IllegalAccessException var12) {
                this.logger.error("get target data-sources exception:", var12);
                throw new RuntimeException(String.format("get target data-sources exception [%s]", var12.getMessage()));
            }

            if (ObjectUtils.isEmpty(map.get(dataSourceApp))) {
                synchronized (dataSourceApp.intern()) {
                    Object targetDataSource = map.get(dataSourceApp);
                    if (ObjectUtils.isEmpty(targetDataSource)) {
                        Object dataSource = this.createDataSource(this.getAppDataSource(dataSourceApp), dataSourceApp, this.applicationContext, this.maximumPoolSize, this.minimumIdle, this.maxLifeTime, this.connectionTimeout);
                        map.put(dataSourceApp, dataSource);
                        super.afterPropertiesSet();
                    }
                }
            }

            return dataSourceApp;
        }
    }

    public Object createDataSource(HikariDataSourceInfo dateSource, String dataSourceApp, ApplicationContext applicationContext, int maximumPoolSize, int minimumIdle, long maxLifeTime, long connectionTimeout) {
        ConfigurableApplicationContext context = (ConfigurableApplicationContext) applicationContext;
        DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) context.getBeanFactory();
        BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(HikariDataSource.class);
        beanDefinitionBuilder.addPropertyValue("username", dateSource.getUsername());
        beanDefinitionBuilder.addPropertyValue("password", dateSource.getPassword());
        beanDefinitionBuilder.addPropertyValue("driverClassName", dateSource.getDriverClassName());
        beanDefinitionBuilder.addPropertyValue("jdbcUrl", dateSource.getJdbcUrl());
        beanDefinitionBuilder.addPropertyValue("maximumPoolSize", Math.max(maximumPoolSize, 20));
        beanDefinitionBuilder.addPropertyValue("minimumIdle", minimumIdle);
        beanDefinitionBuilder.addPropertyValue("poolName", "HikariPool-" + dataSourceApp);
        beanDefinitionBuilder.addPropertyValue("maxLifetime", maxLifeTime);
        beanDefinitionBuilder.addPropertyValue("idleTimeout", maxLifeTime - 30000L);
        beanDefinitionBuilder.addPropertyValue("connectionTimeout", connectionTimeout);
        beanFactory.registerBeanDefinition(dataSourceApp, beanDefinitionBuilder.getBeanDefinition());
        return applicationContext.getBean(dataSourceApp);
    }

    private HikariDataSourceInfo getAppDataSource(String dataSourceApp) {
        HikariDataSourceInfo dataSourceInfo = DynamicDataSourceUtil.getDynamicSource(dataSourceApp);

        try {
            this.connectionTest(dataSourceInfo);
            return dataSourceInfo;
        } catch (SQLException var9) {
            this.logger.error(String.format("get app[%s] data-sources connection test SQLException: ", dataSourceApp), var9);
            throw new RuntimeException(String.format("get app[%s] data-sources connection test SQLException:[%s]", dataSourceApp, var9.getMessage()));
        } catch (ClassNotFoundException var10) {
            this.logger.error(String.format("get app[%s] data-sources connection test ClassNotFoundException: ", dataSourceApp), var10);
            throw new RuntimeException(String.format("get app[%s] data-sources connection test ClassNotFoundException:[%s]", dataSourceApp, var10.getMessage()));
        }
    }

    public boolean connectionTest(HikariDataSourceInfo dataSourceInfo) throws SQLException, ClassNotFoundException {
        String driverClassName = dataSourceInfo.getDriverClassName();
        String url = dataSourceInfo.getJdbcUrl();
        Connection conn = null;

        try {
            Class.forName(driverClassName);
            conn = DriverManager.getConnection(url, dataSourceInfo.getUsername(), dataSourceInfo.getPassword());
        } finally {
            if (conn != null && !conn.isClosed()) {
                conn.close();
            }
        }

        return true;
    }


    private Map<Object, Object> getTargetDataSources() throws NoSuchFieldException, IllegalAccessException {
        Field field = AbstractRoutingDataSource.class.getDeclaredField("targetDataSources");
        field.setAccessible(true);
        return (Map) field.get(this);
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}
